﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;

namespace Particle_Editor
{
    class Emitter
    {
        public String SEmitterName { get; set; }
        public String SFileName { get; set; }
        public Vector emitterNormal;
        public int IMaxParticles { get; set; }
        public float FSpawnRate { get; set; }
        public float FSpawnTime { get; set; }
        public bool BRepeat { get; set; }

        //public Bitmap Bitmap { get; set; }
        
        public Flyweight flyweight = new Flyweight();
        public List<Particle> liveList = new List<Particle>();
        public List<Particle> deadList = new List<Particle>();

        // Needed for C#
        public int ImageID { get; set; }

        public Emitter(String filePath, String name = "Default")
        {
            SFileName = filePath;
            SEmitterName = name;
            IMaxParticles = 100;
            FSpawnRate = 0.02f;
            FSpawnTime = 0.0f;
            emitterNormal = new Vector(0.0f, -1.0f);
            BRepeat = true;
            for (int i = 0; i < IMaxParticles; ++i)
            {
                deadList.Add(new Particle());
            }
        }

        public int Update(float dt)
        {
            FSpawnTime -= dt;

            while ((deadList.Count + liveList.Count) < IMaxParticles)
            {
                deadList.Add(new Particle());
            }

            // Spawn new particle if not at max
            if (FSpawnTime <= 0)
            {
                if (liveList.Count < IMaxParticles)
                {
                    FSpawnTime = FSpawnRate;
                    deadList[0].Reset(flyweight);
                    liveList.Add(deadList[0]);
                    deadList.RemoveAt(0);
                }
            }

            // Update existing live particles
            for (int i = 0; i < liveList.Count; ++i)
            {
                // Increase living counter by dt
                liveList[i].FLifeCurrent += dt;

                // Check if current lifetime exceeds max life time
                if (liveList[i].FLifeCurrent >= liveList[i].FLifeEnd)
                {
                    // If repeating add particle to deadList
                    if (BRepeat)
                    {
                        deadList.Add(liveList[i]);
                    }

                    // Remove particle from liveList
                    liveList.RemoveAt(i);
                }
                else
                {
                    // Update the particle
                    liveList[i].Update(flyweight, dt);
                }
            }
            return 1;
        }
    }

    class Vector
    {

        public float X { get; set; }
        public float Y { get; set; }

        public Vector(float _x, float _y)
        {
            X = _x;
            Y = _y;
        }

        public void Normalize()
        {
            float length = (float)Math.Sqrt(X + Y);
            X /= length;
            Y /= length;
        }

        public void Rotate(float deg)
        {
            // Convert degrees to radians
            deg = (float)((Math.PI / 180.0f) * deg);

            float sin = (float)Math.Sin(deg);
            float cos = (float)Math.Cos(deg);

            float newX = (cos * X) - (sin * Y);
            Y = (sin * X) + (cos * Y);
            X = newX;
        }

        public float GetRotation() // Returns angle in degrees
        {
            // New vector pointing up (Unit Vector magnitude = 1)
            Vector ij = new Vector(0.0f, -1.0f);
            
            // Cross multiply
            float dotProduct = ij.X * X + ij.Y * Y;

            if (dotProduct == 0)
                return 0.0f;

            // Find magnitude of vector
            float mag = (float)Math.Sqrt(Math.Pow(X, 2) + Math.Pow(Y, 2));

            float deg = (float)Math.Acos(dotProduct / mag);

            deg = (float)(deg * (180.0f / Math.PI));
            return deg;
        }
    }

    class Flyweight
    {

        public enum EmitterType : int { POINT = 0, LINE, CIRCLE, RECTANGLE, };

        public int IEmitterType { get; set; }
        public PointF Position { get; set; }
        public Rectangle drawRect { get; set; }
        public PointF centerPoint { get; set; }

        public int IEmitterWidth { get; set; }
        public int IEmitterHeight { get; set; }
        public int IEmitterRange { get; set; }

        public float FAngle { get; set; }

        public float FMinLife { get; set; }
        public float FMaxLife { get; set; }
        
        public float FStartColorA { get; set; }
        public float FStartColorR { get; set; }
        public float FStartColorG { get; set; }
        public float FStartColorB { get; set; }
        public float FEndColorA { get; set; }
        public float FEndColorR { get; set; }
        public float FEndColorG { get; set; }
        public float FEndColorB { get; set; }
        
        public float FStartScale { get; set; }
        public float FEndScale { get; set; }
        
        public float FRotationChangePerSec { get; set; }
        
        public float FStartSpeed { get; set; }
        public float FEndSpeed { get; set; }

        public float FParticleMass { get; set; }

        public bool BGravity { get; set; }
        public bool BWind { get; set; }
        public Random rand;

        public Flyweight()
        {
            IEmitterType = (int)EmitterType.LINE;
            Position = new PointF(250.0f, 250.0f);
            IEmitterWidth = 1;
            IEmitterHeight = 1;
            rand = new Random();
            IEmitterRange = 0;
            FMinLife = 5.0f;
            FMaxLife = 5.0f;
            FStartColorA = 1.0f;
            FStartColorR = 0.0f;
            FStartColorG = 0.0f;
            FStartColorB = 1.0f;
            FRotationChangePerSec = 0.0f;
            FAngle = 0.0f;
            FStartSpeed = 0.1f;
            FEndSpeed = 0.1f;
            FStartScale = 1.0f;
            FEndScale = 1.0f;
            FParticleMass = 0.1f;
            BGravity = false;
            BWind = false;
        }
    }

    class Particle
    {
        public Vector Velocity { get; set; }
        public Vector Acceleration { get; set; }
        public PointF Position { get; set; }

        public float FLifeEnd { get; set; }
        public float FLifeCurrent { get; set; }

        public float FRotationCurrent { get; set; }

        public float FColorCurrentA { get; set; }
        public float FColorCurrentR { get; set; }
        public float FColorCurrentG { get; set; }
        public float FColorCurrentB { get; set; }
        public float FColorChangePerSecA { get; set; }
        public float FColorChangePerSecR { get; set; }
        public float FColorChangePerSecG { get; set; }
        public float FColorChangePerSecB { get; set; }
        
        public float FScaleCurrent { get; set; }
        public float FScaleChangePerSec { get; set; }

        public float FSpeedCurrent { get; set; }
        public float FSpeedChangePerSec { get; set; }
        
        public Color CColor { get; set; }

        public void Update(Flyweight flyweight, float dt)
        {
            // Update rotation
            if (flyweight.FRotationChangePerSec != 0)
                FRotationCurrent += (flyweight.FRotationChangePerSec * dt);

            // Update scale
            if (FScaleChangePerSec != 0)
                FScaleCurrent += (FScaleChangePerSec * dt);

            // Update speed
            if (FSpeedChangePerSec != 0)
                FSpeedCurrent += (FSpeedChangePerSec * dt);

            // Update acceleration
            float angle = Acceleration.GetRotation();
            Acceleration = new Vector(0.0f, -1.0f * FSpeedCurrent);
            Acceleration.Rotate(angle);

            // Apply Gravity?
            if (flyweight.BGravity)
            {
                Vector Fnet = new Vector(Acceleration.X, Acceleration.Y  * flyweight.FParticleMass);
                Fnet = new Vector(Fnet.X, Fnet.Y + 1.45f);
                Acceleration = new Vector(Fnet.X, Fnet.Y / flyweight.FParticleMass);
            }

            // Update Velocity
            Velocity = new Vector(Velocity.X + (Acceleration.X * dt), Velocity.Y + (Acceleration.Y * dt));

            // Update Position
            Position = new PointF(Position.X + (Velocity.X * dt), Position.Y + (Velocity.Y * dt));

            // Apply color change
            FColorCurrentA += FColorChangePerSecA * dt;
            FColorCurrentR += FColorChangePerSecR * dt;
            FColorCurrentG += FColorChangePerSecG * dt;
            FColorCurrentB += FColorChangePerSecB * dt;

            // Restrain Color values to less than or equal to 1.0
            if (FColorCurrentA > 1.0f)
                FColorCurrentA = 1.0f;
            if (FColorCurrentR > 1.0f)
                FColorCurrentR = 1.0f;
            if (FColorCurrentG > 1.0f)
                FColorCurrentG = 1.0f;
            if (FColorCurrentB > 1.0f)
                FColorCurrentB = 1.0f;

            // Restrain Color values to greater than or equal to 0.0
            if (FColorCurrentA < 0.0f)
                FColorCurrentA = 0.0f;
            if (FColorCurrentR < 0.0f)
                FColorCurrentR = 0.0f;
            if (FColorCurrentG < 0.0f)
                FColorCurrentG = 0.0f;
            if (FColorCurrentB < 0.0f)
                FColorCurrentB = 0.0f;

            // Create color from ARGB values
            CColor = Color.FromArgb((int)(FColorCurrentA * 255), (int)(FColorCurrentR * 255), (int)(FColorCurrentG * 255), (int)(FColorCurrentB * 255));
        }

        public void Reset(Flyweight flyweight)
        {
            switch (flyweight.IEmitterType)
            {
               case 1: // Line
                    {
                        // Random spot based off of width
                        int spawn = flyweight.rand.Next(0, flyweight.IEmitterWidth);
                        spawn -= flyweight.IEmitterWidth >> 1;

                        Vector pos = new Vector((float)spawn, 0.0f);
                        pos.Rotate(flyweight.FAngle);

                        Position = new PointF(flyweight.Position.X + pos.X, flyweight.Position.Y + pos.Y);
                        break;
                    }
                case 2: // Circle
                    {
                        // Random spot based off of width (radius)
                        int xSpawn = flyweight.rand.Next(0, (flyweight.IEmitterWidth >> 1));
                        // Random rotation
                        int rot = flyweight.rand.Next(0, 359);

                        Vector pos = new Vector((float)xSpawn, 0.0f);
                        pos.Rotate(rot);

                        Position = new PointF(flyweight.Position.X + pos.X, flyweight.Position.Y + pos.Y);
                        break;
                    }
                case 3: // Rectangle
                    {
                        // Random spot based off of width
                        int spawnX = flyweight.rand.Next(0, flyweight.IEmitterWidth);
                        spawnX -= flyweight.IEmitterWidth >> 1;
                        int spawnY = flyweight.rand.Next(0, flyweight.IEmitterHeight);
                        spawnY -= flyweight.IEmitterHeight >> 1;

                        Position = new PointF(flyweight.Position.X + spawnX, flyweight.Position.Y + spawnY);
                        break;
                    }
                case 0: // Point
                default:
                    {
                        // Set the position
                        Position = new PointF(flyweight.Position.X, flyweight.Position.Y);
                        break;
                    }
            }
            // Randomize the Particle life
            int min = (int)(flyweight.FMinLife * 100);
            int max = (int)((flyweight.FMinLife + flyweight.FMaxLife) * 100);
            FLifeEnd = (float)(flyweight.rand.Next(min, max)) / 100;

            // Set speed of particle
            FSpeedCurrent = flyweight.FStartSpeed;
            FSpeedChangePerSec = (flyweight.FEndSpeed - flyweight.FStartSpeed) / FLifeEnd;

     
            // Set the particle color
            FColorCurrentA = flyweight.FStartColorA;
            FColorCurrentR = flyweight.FStartColorR;
            FColorCurrentG = flyweight.FStartColorG;
            FColorCurrentB = flyweight.FStartColorB;

            FColorChangePerSecA = (flyweight.FEndColorA - flyweight.FStartColorA) / FLifeEnd;
            FColorChangePerSecR = (flyweight.FEndColorR - flyweight.FStartColorR) / FLifeEnd;
            FColorChangePerSecG = (flyweight.FEndColorG - flyweight.FStartColorG) / FLifeEnd;
            FColorChangePerSecB = (flyweight.FEndColorB - flyweight.FStartColorB) / FLifeEnd;

            // Create color from ARGB values
            CColor = Color.FromArgb((int)(FColorCurrentA * 255), (int)(FColorCurrentR * 255), (int)(FColorCurrentG * 255), (int)(FColorCurrentB * 255));
            
            // Set current life to 0
            FLifeCurrent = 0.0f;

            // Set Rotation and Scale
            FRotationCurrent = 0.0f;

            FScaleCurrent = flyweight.FStartScale;
            FScaleChangePerSec = (flyweight.FEndScale - flyweight.FStartScale) / FLifeEnd;

            // Set Acceleration
            Acceleration = new Vector(0.0f, -1.0f * FSpeedChangePerSec);
            Acceleration.Rotate(flyweight.FAngle);

            // Load normal of Emitter into Particle Normal
            Vector normal = new Vector(0.0f, -1.0f * FSpeedCurrent);
            normal.Rotate(flyweight.FAngle);
            Velocity = normal;

            // Rotate particle normal randomly based on range
            int range = flyweight.rand.Next(flyweight.IEmitterRange);
            Velocity.Rotate(range - (flyweight.IEmitterRange >> 1));
        }
    }
}

